AWS SAM でビルドは成功しているが、ビルド前の成果物がデプロイされる時はファイルが使い分けられているか確認しよう

AWS SAM でビルドは成功しているが、ビルド前の成果物がデプロイされる時はファイルが使い分けられているか確認しよう

Clock Icon2025.01.01

こんにちは! AWS 事業本部コンサルティング部のたかくに(@takakuni_)です。

先日、SAM を使ってビルド処理を行う必要がありました。ビルドは成功しているものの、ビルド前の成果物がデプロイされ詰まったためブログにします。

問題

早速ですが問題です。

以下の構成で Lambda Layer を作成しているとします。

takakuni@ samblog % tree -L4 -a
.
├── .aws-sam
│   ├── build
│   │   ├── Requests
│   │   │   └── python
│   │   └── template.yaml
│   └── build.toml
├── layers
│   └── Requests
│       └── requirements.txt
├── samconfig.yaml
└── template.yaml

7 directories, 5 files

template.yaml はシンプルに Layer を作るだけにしています。

template.yaml
Transform: AWS::Serverless-2016-10-31
Description: SAM Template
Resources:
  Requests:
    Type: AWS::Serverless::LayerVersion
    Metadata:
      BuildMethod: python3.12
      SamResourceId: Requests
    Properties:
      LayerName: 'Requests'
      Description: 'Requests'
      CompatibleArchitectures:
        - x86_64
      CompatibleRuntimes:
        - python3.12
      ContentUri: './layers/Requests'
      RetentionPolicy: Delete

samconfig.yamltemplate_file には何が入るでしょうか?

  1. template.yaml
  2. .aws-sam/build/template.yaml
samconfig.yaml
version: 0.1
default:
  build:
    parameters:
      cached: false
      debug: true
      mount_with: READ
takakuni:
  deploy:
    parameters:
      stack_name: 'lambda-layer-sample'
+      template_file: '?'
      on_failure: 'ROLLBACK'
      max_wait_duration: 60
      capabilities:
        - 'CAPABILITY_IAM'
        - 'CAPABILITY_NAMED_IAM'
      profile: 'takakuni'
      resolve_s3: true
      s3_prefix: 'samsam'

正解は 2 番の .aws-sam/build/template.yaml です。

ContentUri

AWS::Serverless::LayerVersion の ContentUri をみてみましょう。

「テンプレートは sam build の後に sam deploy または sam package を含むワークフローを通過しなければならない。」と記載されていますね。

これは一体どういうことなのでしょうか。

ContentUri
Amazon S3 Uri, path to local folder, or LayerContent object of the layer code.

If an Amazon S3 Uri or LayerContent object is provided, The Amazon S3 object referenced must be a valid ZIP archive that contains the contents of an Lambda layer.

If a path to a local folder is provided, for the content to be transformed properly the template must go through the workflow that includes sam build followed by either sam deploy or sam package. By default, relative paths are resolved with respect to the AWS SAM template's location.

Type: String | LayerContent

Required: Yes

AWS CloudFormation compatibility: This property is similar to the Content property of an AWS::Lambda::LayerVersion resource. The nested Amazon S3 properties are named differently.

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-resource-layerversion.html#sam-layerversion-contenturi

ビルドした成果物

sam build は Python の場合、先ほどの ContentUri で指定したディレクトリの requirements.txt を参照し pip install が行われます。最終的な成果物はデフォルトで .aws-sam/build に保管されます。

takakuni@HL01556 samblog % sam build -t template.yaml --debug
2025-01-01 22:10:15,618 | Config file location: /Users/takakuni/Documents/sample/samblog/samconfig.yaml
2025-01-01 22:10:15,620 | Loading configuration values from [default.['build'].parameters] (env.command_name.section) in config file at
'/Users/takakuni/Documents/sample/samblog/samconfig.yaml'...
2025-01-01 22:10:15,620 | Configuration values successfully loaded.
2025-01-01 22:10:15,621 | Configuration values are: {'cached': False, 'debug': True, 'mount_with': 'READ'}
2025-01-01 22:10:15,635 | OSError occurred while reading TOML file: [Errno 2] No such file or directory: '/Users/takakuni/Documents/sample/samblog/samconfig.toml'
2025-01-01 22:10:15,635 | Using config file: samconfig.toml, config environment: default
2025-01-01 22:10:15,636 | Expand command line arguments to:
2025-01-01 22:10:15,636 | --template_file=/Users/takakuni/Documents/sample/samblog/template.yaml --mount_with=READ --build_dir=.aws-sam/build --cache_dir=.aws-sam/cache
2025-01-01 22:10:15,651 | 'build' command is called
2025-01-01 22:10:15,653 | No Parameters detected in the template
2025-01-01 22:10:15,665 | Sam customer defined id is more priority than other IDs. Customer defined id for resource Requests is Requests
2025-01-01 22:10:15,665 | 0 stacks found in the template
2025-01-01 22:10:15,666 | No Parameters detected in the template
2025-01-01 22:10:15,675 | Sam customer defined id is more priority than other IDs. Customer defined id for resource Requests is Requests
2025-01-01 22:10:15,676 | 1 resources found in the stack
2025-01-01 22:10:15,676 | --base-dir is not presented, adjusting uri ./layers/Requests relative to /Users/takakuni/Documents/sample/samblog/template.yaml
2025-01-01 22:10:15,688 | 1 resources found in the stack
2025-01-01 22:10:15,689 | Instantiating build definitions
2025-01-01 22:10:15,691 | Same Layer build definition found, adding layer (Previous: LayerBuildDefinition(Requests,
/Users/takakuni/Documents/sample/samblog/layers/Requests, , 60f34ec0-e210-4832-91ae-6ba691764a05, python3.12, ['python3.12'], x86_64, {}), Current:
LayerBuildDefinition(Requests, /Users/takakuni/Documents/sample/samblog/layers/Requests, , 361efbcc-56ec-45fb-bf37-2d6826866b3f, python3.12, ['python3.12'], x86_64, {}),
Layer: <samcli.lib.providers.provider.LayerVersion object at 0x105bf6d90>)
2025-01-01 22:10:15,692 | Building layer 'Requests'
2025-01-01 22:10:15,693 | Loading workflow module 'aws_lambda_builders.workflows'
2025-01-01 22:10:15,694 | Registering workflow 'CustomMakeBuilder' with capability 'Capability(language='provided', dependency_manager=None, application_framework=None)'
2025-01-01 22:10:15,695 | Registering workflow 'DotnetCliPackageBuilder' with capability 'Capability(language='dotnet', dependency_manager='cli-package',
application_framework=None)'
2025-01-01 22:10:15,696 | Registering workflow 'GoModulesBuilder' with capability 'Capability(language='go', dependency_manager='modules', application_framework=None)'
2025-01-01 22:10:15,697 | Registering workflow 'JavaGradleWorkflow' with capability 'Capability(language='java', dependency_manager='gradle', application_framework=None)'
2025-01-01 22:10:15,698 | Registering workflow 'JavaMavenWorkflow' with capability 'Capability(language='java', dependency_manager='maven', application_framework=None)'
2025-01-01 22:10:15,699 | Registering workflow 'NodejsNpmBuilder' with capability 'Capability(language='nodejs', dependency_manager='npm', application_framework=None)'
2025-01-01 22:10:15,700 | Registering workflow 'NodejsNpmEsbuildBuilder' with capability 'Capability(language='nodejs', dependency_manager='npm-esbuild',
application_framework=None)'
2025-01-01 22:10:15,702 | Registering workflow 'PythonPipBuilder' with capability 'Capability(language='python', dependency_manager='pip', application_framework=None)'
2025-01-01 22:10:15,703 | Registering workflow 'RubyBundlerBuilder' with capability 'Capability(language='ruby', dependency_manager='bundler', application_framework=None)'
2025-01-01 22:10:15,704 | Registering workflow 'RustCargoLambdaBuilder' with capability 'Capability(language='rust', dependency_manager='cargo', application_framework=None)'
2025-01-01 22:10:15,716 | Found workflow 'PythonPipBuilder' to support capabilities 'Capability(language='python', dependency_manager='pip', application_framework=None)'
2025-01-01 22:10:15,916 | Running workflow 'PythonPipBuilder'
2025-01-01 22:10:15,917 |  Running PythonPipBuilder:ResolveDependencies
2025-01-01 22:10:16,154 | calling pip download -r /Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt --dest
/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik --exists-action i
2025-01-01 22:10:16,740 | pip stdout: b'Collecting requests (from -r /Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n  Using cached
requests-2.32.3-py3-none-any.whl.metadata (4.6 kB)\nCollecting charset-normalizer<4,>=2 (from requests->-r
/Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n  Using cached
charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl.metadata (35 kB)\nCollecting idna<4,>=2.5 (from requests->-r
/Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n  Using cached idna-3.10-py3-none-any.whl.metadata (10 kB)\nCollecting
urllib3<3,>=1.21.1 (from requests->-r /Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n  Using cached
urllib3-2.3.0-py3-none-any.whl.metadata (6.5 kB)\nCollecting certifi>=2017.4.17 (from requests->-r
/Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n  Using cached certifi-2024.12.14-py3-none-any.whl.metadata (2.3 kB)\nUsing cached
requests-2.32.3-py3-none-any.whl (64 kB)\nUsing cached certifi-2024.12.14-py3-none-any.whl (164 kB)\nUsing cached charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl
(196 kB)\nUsing cached idna-3.10-py3-none-any.whl (70 kB)\nUsing cached urllib3-2.3.0-py3-none-any.whl (128 kB)\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/requests-2.32.3-py3-none-any.whl\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/certifi-2024.12.14-py3-none-any.whl\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/idna-3.10-py3-none-any.whl\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/urllib3-2.3.0-py3-none-any.whl\nSuccessfully downloaded requests certifi charset-normalizer idna urllib3\n'
2025-01-01 22:10:16,743 | pip stderr: b"WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.\nPlease see
https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.\nTo avoid this problem you can invoke Python with '-m pip' instead of running pip directly.\n"
2025-01-01 22:10:16,744 | Full dependency closure: {charset-normalizer==3.4.1(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel), idna==3.10(wheel), certifi==2024.12.14(wheel)}
2025-01-01 22:10:16,744 | initial compatible: {idna==3.10(wheel), certifi==2024.12.14(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel)}
2025-01-01 22:10:16,745 | initial incompatible: {charset-normalizer==3.4.1(wheel)}
2025-01-01 22:10:16,745 | Downloading missing wheels: {charset-normalizer==3.4.1(wheel)}
2025-01-01 22:10:16,746 | calling pip download --only-binary=:all: --no-deps --platform manylinux2014_x86_64 --implementation cp --abi cp312 --dest
/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik charset-normalizer==3.4.1
2025-01-01 22:10:17,218 | pip stdout: b'Collecting charset-normalizer==3.4.1\n  Using cached
charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (35 kB)\nUsing cached
charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (145 kB)\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\nSuccessfully downloaded
charset-normalizer\n'
2025-01-01 22:10:17,219 | pip stderr: b"WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.\nPlease see
https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.\nTo avoid this problem you can invoke Python with '-m pip' instead of running pip directly.\n"
2025-01-01 22:10:17,220 | compatible wheels after second download pass: {charset-normalizer==3.4.1(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel), idna==3.10(wheel),
certifi==2024.12.14(wheel)}
2025-01-01 22:10:17,220 | Build missing wheels from sdists (C compiling True): set()
2025-01-01 22:10:17,221 | compatible after building wheels (no C compiling): {charset-normalizer==3.4.1(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel), idna==3.10(wheel),
certifi==2024.12.14(wheel)}
2025-01-01 22:10:17,221 | Build missing wheels from sdists (C compiling False): set()
2025-01-01 22:10:17,222 | compatible after building wheels (C compiling): {charset-normalizer==3.4.1(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel), idna==3.10(wheel),
certifi==2024.12.14(wheel)}
2025-01-01 22:10:17,222 | Final compatible: {idna==3.10(wheel), charset-normalizer==3.4.1(wheel), certifi==2024.12.14(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel)}
2025-01-01 22:10:17,223 | Final incompatible: {charset-normalizer==3.4.1(wheel)}
2025-01-01 22:10:17,223 | Final missing wheels: set()
2025-01-01 22:10:17,239 | PythonPipBuilder:ResolveDependencies succeeded
2025-01-01 22:10:17,240 |  Running PythonPipBuilder:CopySource
2025-01-01 22:10:17,241 | Copying source file (/Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt) to destination
(/Users/takakuni/Documents/sample/samblog/.aws-sam/build/Requests/python/requirements.txt)
2025-01-01 22:10:17,242 | PythonPipBuilder:CopySource succeeded
2025-01-01 22:10:17,243 | Sam customer defined id is more priority than other IDs. Customer defined id for resource Requests is Requests
2025-01-01 22:10:17,243 | 1 resources found in the stack

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
2025-01-01 22:10:17,245 | Telemetry endpoint configured to be https://aws-serverless-tools-telemetry.us-west-2.amazonaws.com/metrics
2025-01-01 22:10:17,371 | Telemetry endpoint configured to be https://aws-serverless-tools-telemetry.us-west-2.amazonaws.com/metrics
2025-01-01 22:10:17,372 | Sending Telemetry: {'metrics': [{'commandRun': {'requestId': '7f6f6626-6fa7-41de-8e93-6bcf88dd2e78', 'installationId':
'f860202e-7e45-47ad-bc4f-27c0add960f5', 'sessionId': 'd2ecc91d-9c0a-4ed8-90d9-9f23e65de34c', 'executionEnvironment': 'CLI', 'ci': False, 'pyversion': '3.8.20', 'samcliVersion':
'1.126.0', 'awsProfileProvided': False, 'debugFlagProvided': True, 'region': '', 'commandName': 'sam build', 'metricSpecificAttributes': {'projectType': 'CFN', 'gitOrigin': None,
'projectName': '9b56b0810290e646069522dc410faab3274b3b5661821ad30efefa48e2160bd2', 'initialCommit': None}, 'duration': 1609, 'exitReason': 'success', 'exitCode': 0}}]}
2025-01-01 22:10:17,372 | Unable to find Click Context for getting session_id.
2025-01-01 22:10:17,375 | Sending Telemetry: {'metrics': [{'events': {'requestId': '1fc0c680-a2ff-4841-a5e1-7d3f947b5bc9', 'installationId': 'f860202e-7e45-47ad-bc4f-27c0add960f5',
'sessionId': 'd2ecc91d-9c0a-4ed8-90d9-9f23e65de34c', 'executionEnvironment': 'CLI', 'ci': False, 'pyversion': '3.8.20', 'samcliVersion': '1.126.0', 'commandName': 'sam build',
'metricSpecificAttributes': {'events': [{'event_name': 'SamConfigFileExtension', 'event_value': '.yaml', 'thread_id': '43a30774f14848e39283b9dfe91609e2', 'time_stamp': '2025-01-01
13:10:15.618', 'exception_name': None}, {'event_name': 'SamConfigFileExtension', 'event_value': '.toml', 'thread_id': '108882b733c24f7a93aee7c989613760', 'time_stamp': '2025-01-01
13:10:15.635', 'exception_name': None}, {'event_name': 'BuildWorkflowUsed', 'event_value': 'python-pip', 'thread_id': '626eafa3a0c542519d2310a17ba50ede', 'time_stamp': '2025-01-01
13:10:15.693', 'exception_name': None}]}}}]}
2025-01-01 22:10:17,728 | HTTPSConnectionPool(host='aws-serverless-tools-telemetry.us-west-2.amazonaws.com', port=443): Read timed out. (read timeout=0.1)
2025-01-01 22:10:17,782 | HTTPSConnectionPool(host='aws-serverless-tools-telemetry.us-west-2.amazonaws.com', port=443): Read timed out. (read timeout=0.1)

デプロイ

sam build が成功したので「テンプレートは sam build の後に sam deploy または sam package を含むワークフローを通過しなければならない。」のデプロイに進みます。

さきほどの samconfig.yaml に戻ります。試しに誤りの template.yaml を指定するとします。

samconfig.yaml
version: 0.1
default:
  build:
    parameters:
      cached: false
      debug: true
      mount_with: READ
takakuni:
  deploy:
    parameters:
      stack_name: 'lambda-layer-sample'
      template_file: 'template.yaml'
      on_failure: 'ROLLBACK'
      max_wait_duration: 60
      capabilities:
        - 'CAPABILITY_IAM'
        - 'CAPABILITY_NAMED_IAM'
      profile: 'takakuni'
      resolve_s3: true
      s3_prefix: 'samsam'

この場合レイヤーに登録されるのは ContentUri に指定した ./layers/Requests の内容(ビルド前の requirements.txt のみ)になります。

takakuni@ samblog % tree -L4 -a
.
├── .aws-sam
│   ├── build
│   │   ├── Requests
│   │   │   └── python
│   │   └── template.yaml
│   └── build.toml
├── layers
│   └── Requests
│       └── requirements.txt
├── samconfig.yaml
└── template.yaml

7 directories, 5 files

つまり、この ContentUri は

  1. sam build コマンドの時はビルド前の状態を指定しておくこと
  2. sam deploy コマンドの時はビルド後の状態を指定しておくこと

の 2 つの要件が必要になります。これが「テンプレートは sam build の後に sam deploy または sam package を含むワークフローを通過しなければならない。」です。

コマンド実行時のファイル使い分け

先ほどの sam buildsam deploy コマンドを 1 つのファイルの ContentUri で使い分けるのは難しいです。

そのため、コマンド別にファイルを使い分けます。

template.yaml.aws-sam/build/template.yaml の中身を見てみましょう。 ContentUri が若干違いますね。

template.yaml
Transform: AWS::Serverless-2016-10-31
Description: SAM Template
Resources:
  Requests:
    Type: AWS::Serverless::LayerVersion
    Metadata:
      BuildMethod: python3.12
      SamResourceId: Requests
    Properties:
      LayerName: 'Requests'
      Description: 'Requests'
      CompatibleArchitectures:
        - x86_64
      CompatibleRuntimes:
        - python3.12
      ContentUri: './layers/Requests'
      RetentionPolicy: Delete

.aws-sam/build/template.yaml はビルドした成果物のパスを見ています。

.aws-sam/build/template.yaml
Transform: AWS::Serverless-2016-10-31
Description: SAM Template
Resources:
  Requests:
    Type: AWS::Serverless::LayerVersion
    Metadata:
      BuildMethod: python3.12
      SamResourceId: Requests
    Properties:
      LayerName: Requests
      Description: Requests
      CompatibleArchitectures:
      - x86_64
      CompatibleRuntimes:
      - python3.12
+     ContentUri: Requests
      RetentionPolicy: Delete

よって、samconfig.yaml には .aws-sam/build/template.yaml を指定します。

samconfig.yaml
version: 0.1
default:
  build:
    parameters:
      cached: false
      debug: true
      mount_with: READ
takakuni:
  deploy:
    parameters:
      stack_name: 'lambda-layer-sample'
      template_file: '.aws-sam/build/template.yaml'
      on_failure: 'ROLLBACK'
      max_wait_duration: 60
      capabilities:
        - 'CAPABILITY_IAM'
        - 'CAPABILITY_NAMED_IAM'
      profile: 'takakuni'
      resolve_s3: true
      s3_prefix: 'samsam'

sam build が参照するコマンドを見てみます。 --template-file で指定できますがデフォルトでは template.yaml|yml が参照されます。

--template-file, --template, -t PATH
The path and file name of AWS SAM template file [default: template.[yaml|yml]]. This option is not compatible with --hook-name.

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-build.html

相関関係を表した図が以下になります。

Untitled(109)

なぜ本エントリを書くきっかけになったのか

sam deploy で template.yaml を指定したいケースは、今回のようなコマンドごとにテンプレートの使い分けが求められるようです。

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html

まとめ

以上、「AWS SAM でビルドは成功しているが、ビルド前の成果物がデプロイされる時はファイルが使い分けられているか確認しよう」でした。

ビルドした成果物がレイヤーにうまく登録されいないケースは、ありがちだと思うので備忘録的に書いてみました。

クラウド事業本部コンサルティング部のたかくに(@takakuni_)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.